可视化和绘图是数据分析中重要的任务之一,可以看作是数据分析的最后一个步骤。Python有很多附加库可以用来制作静态或动态的可视化文件:
尽管Python目前已发展出许多绘图库进行可视化,但Matplotlib的地位仍然不可撼动,其创建之初就是一个用于生成出版级质量图表的绘图包,因此我们仍然需要学习Matplotlib进行绘图。
# Sample with Matplotlib
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 2, 100) # linspace()函数返回100个0-2均匀分布的样本
plt.plot(x, x, label='linear')
plt.plot(x, x**2,label='quadratic')
plt.plot(x, x**3, label='cubic')
plt.title('Simple Plot')
plt.xlabel('x 轴')
plt.ylabel('y 轴')
plt.legend()
plt.show()
/Users/yvonne/opt/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 36724 (\N{CJK UNIFIED IDEOGRAPH-8F74}) missing from current font.
fig.canvas.print_figure(bytes_io, **kw)
# Sample with Plotly
# 导入所需的模块
import numpy as np
import plotly.graph_objects as go
x = np.linspace(0,2,100)
# 使用go.Figure()函数创建一个图形fig
fig = go.Figure()
# 使用add_trace()函数向fig中添加trace(画轨、画迹、画痕)
fig.add_trace(go.Scatter(x=x,y=x,name='linear'))
fig.add_trace(go.Scatter(x=x,y=x**2,name='quadratic'))
fig.add_trace(go.Scatter(x=x,y=x**3,name='cubic'))
# 使用update_layout()函数设置fig的画面布局
fig.update_layout(
title='Simple Plot',
xaxis=dict(title='x轴'),
yaxis=dict(title='y轴'))
# 将图形fig输出
fig.show()
针对Matplotlib存在的不足,我们通过以下两个方法来解决。
%matplotlib notebook,即可在jupyter notebook行内形成交互式的图表。如果无需交互,也可通过魔术指令%config InlineBackend.figure_format = 'retina'生成高精度图片。plt.rcParams['font.sans-serif'] = ['SimHei'],指定中文后负号无法正常显示,需再添加plt.rcParams['axes.unicode_minus'] = FalseMac电脑解决方案:
%matplotlib,弹出精度高、可交互对话框。如果无需交互,也可通过魔术指令%config InlineBackend.figure_format = 'retina'生成高精度图片。matplotlib.font_manager.fontManager.ttflist查看以安装的字体,然后使用以下两句话实现支持中文字符和显示负号:
plt.rcParams['font.sans-serif'] = ['Heiti TC']
plt.rcParams['axes.unicode_minus'] = False注意,操作系统不同,IDE不同(Jupyter Notebook, JupyterLab, VSCode, PyCharm),所使用的魔术指令会有不同,大家可自行查找并解决。
# Sample with Matplotlib
import matplotlib.pyplot as plt
import numpy as np
# %matplotlib notebook
%config InlineBackend.figure_format = 'retina'
x = np.linspace(0, 2, 100) # linspace()函数返回100个0-2均匀分布的样本
plt.plot(x, x, label='linear')
plt.plot(x, x**2,label='quadratic')
plt.plot(x, x**3, label='cubic')
plt.title('Simple Plot')
plt.xlabel('x 轴')
plt.ylabel('y 轴')
plt.rcParams['font.sans-serif'] = ['Heiti TC'] # 设置中文字体,可以更改为'SimHei'/'DengXian'
plt.rcParams['axes.unicode_minus'] = False # 设置成中文字体后正常显示负号
plt.tight_layout() # 紧密布局,使中文标题和文字完整显示
plt.legend()
plt.show()
根据引例,绘制一张图片(一对轴)时的绘图流程可以总结如下:
import matplotlib.pyplot as plt,并在Jupyter notebook中执行魔术指令:%matplotlib notebook;plt.plot()绘制线形图,plt.bar()绘制柱状图,plt.scatter()绘制散点图,plt.hist()绘制直方图……;plt.title()设置标题,plt.xlabel(),plt.ylabel()设置X\Y轴标签,plt.legend()显示图例,plt.annotate()设置注释文本……;plt.rcParams['font.sans-serif'] = ['SimHei'],指定中文后负号无法正常显示,需再添加plt.rcParams['axes.unicode_minus'] = False;plt.show()将图片显示出来,使用plt.savefig()保存图片。绘制的图片结构如下图所示。
# 根据上述绘图基本框架,再写一次引例并进一步细化
import numpy as np
# 绘图模块的导入惯例以及魔术指令
import matplotlib.pyplot as plt
%matplotlib notebook
x = np.linspace(0, 2, 100)
# ----------**-----以下是使用plt调用各类函数绘制基本图形-----**-----------
# 使用.plot方法绘制多条线形图,第一次调用.plot时会创建轴,随后的多次调用.plot会在相同的轴内绘制线形图
plt.plot(x, x, label='linear')
plt.plot(x, x**2,label='quadratic')
plt.plot(x, x**3, label='cubic') # 每条线形图设置了标签label
# ----------**-----以下是使用plt调用其它函数进一步丰富图片-----**-----------
# 设置图片的标题
plt.title('Simple Plot', fontsize=16, color='black')
# 设置X/Y轴的标题
plt.xlabel('x 轴')
plt.ylabel('y 轴')
# 设置X/Y轴的刻度
plt.xticks(ticks=np.arange(0,2.2,0.2))
plt.yticks(ticks=[0,2,4,6,8])
# 设置X/Y轴的范围
# plt.xlim=[0,1]
# plt.ylim=[0,6]
# 显示图例
plt.legend() # 将每条线形图的标签label显示出来
# 显示网格
# plt.grid()
# 添加注释(文本)
# plt.text(1, 1, '交汇点', fontsize=12)
# 添加注释(文本、箭头等)
plt.annotate(text='交汇点', # 注释的文本
xy=(1, 1), # 需要注释的点位置
xytext=(0.8, 2), # 显示注释文本的位置
arrowprops=dict(color='grey',width=2)) # 设置注释箭头
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei'] # 对中文字体进行指定,可以更改字体'SimHei'
plt.rcParams['axes.unicode_minus'] = False # 指定中文字体后正常显示负号
plt.tight_layout() # 紧密布局,使标题、文本等完整显示
# 输出图形
plt.show()
# 保存图形
# plt.savefig('Simple Plot.jpg')
# 根据绘图基本流程,绘制正弦曲线和余弦曲线:
# 1. 使用np.sin()方法和np.cos()方法即可绘制正弦曲线和余弦曲线
# 2. 主标题、轴标题和轴刻度等如图所示(数学符号可用r'$\pi$'显示),最终输出.jpg图片
import numpy as np
import matplotlib.pyplot as plt
%matplotlib notebook
x = np.linspace(0, 2*np.pi, 100)
plt.plot(x, np.sin(x),label='sin曲线')
plt.plot(x, np.cos(x),label='cos曲线')
plt.title('正弦曲线和余弦曲线')
plt.xlabel('X轴')
plt.ylabel('Y轴')
plt.xticks(ticks=np.arange(0,2*np.pi+0.1,step=np.pi/2),
labels=('0',r'$1/2\pi$',r'$\pi$',r'2/3$\pi$',r'2$\pi$'))
# plt.xticks(np.arange(0,2*np.pi+0.1,step=np.pi/2),('0',r'$1/2\pi$',r'$\pi$',r'$3/2\pi$',r'$2\pi$'))
# plt.xticks(ticks=np.arange(0, 2*np.pi+0.1, step=np.pi/2),
# labels=('0', '$1/2\\pi$', '$\\pi$', '2/3$\\pi$', '2$\\pi$'))
plt.legend()
plt.rcParams['font.sans-serif'] = ['SimHei'] # 对中文字体进行指定,可以更改字体'SimHei'
plt.rcParams['axes.unicode_minus'] = False # 指定中文字体后正常显示负号
plt.tight_layout() # 紧密布局,使标题、文本等完整显示
plt.show()
plt.savefig('sin&cos.jpg')
# 生成一个Series对象
import pandas as pd
import numpy as np
N = 50
s = pd.Series(np.random.randn(N))
s.head()
0 1.270455 1 0.845237 2 -0.393019 3 -0.949524 4 -0.144077 dtype: float64
方法一:使用plt.plot()函数进行绘图(基本流程)
# Pandas对象可以在matplotlib画图中作为数据来源放入参数中,如.plot(Series)
plt.plot(s)
[<matplotlib.lines.Line2D at 0x1769905d0>]
方法二:使用pandas对象直接调用.plot()函数
# Pandas对象可以直接调用画图方法,如Series.plot(),index作为x轴数据,values作为y轴数据
s.plot()
<Axes: >
注意:上述语句即使不导入matplotlib也可以绘制,只是存在之前描述的两个不足,仍然可以通过导入matplotlib及其相关语句来解决。
# 生成一个DataFrame对象
import pandas as pd
import numpy as np
N = 100
df = pd.DataFrame([np.random.randn(N),np.random.randn(N)+5,np.random.randn(N)-5]).T
df.columns = ['Plot1','Plot2','Plot3']
df.head()
| Plot1 | Plot2 | Plot3 | |
|---|---|---|---|
| 0 | -1.816063 | 5.567064 | -4.991131 |
| 1 | 0.107912 | 6.298941 | -4.390408 |
| 2 | -1.003578 | 4.658208 | -4.700702 |
| 3 | -0.201100 | 5.898827 | -5.128678 |
| 4 | -1.121558 | 4.238794 | -5.885754 |
方法一:使用plt.plot()函数进行绘图(基本流程)
# Pandas对象可以在matplotlib画图中作为数据来源放入参数中,如.plot(DataFrame)
# 1. 直接传递df的数值一起绘制,但无法分别自定义style和label
plt.plot(df)
[<matplotlib.lines.Line2D at 0x13e30ff90>, <matplotlib.lines.Line2D at 0x13e563a10>, <matplotlib.lines.Line2D at 0x13e563d50>]
# 2. 逐条进行绘制并设置style和label
# 主函数plot接收带有x和y的数组以及一些可选的字符串缩写参数来指定颜色color、标记marker和线类型linestyle
plt.plot(df.index,df['Plot1'],label='Plot1')
plt.plot(df.index,df['Plot2'],'r+',label='Plot2') # 颜色为red,标记为+,线类型为空
# plt.plot(df.index,df['Plot2'],color='red',marker='+',linestyle='',label='Plot2')
plt.plot(df.index,df['Plot3'],'go--',label='Plot3') # 颜色为green,标记为o,线类型为--
# plt.plot(df.index,df['Plot3'],color='green',marker='o',linestyle='dashed',label='Plot3')
plt.legend(loc=1) # 将label显示出来并置于右上角
<matplotlib.legend.Legend at 0x13e641110>
# 3. 写成循环进行绘制
style=['','r+','go--']
for i,s in zip(df.columns,style):
plt.plot(df.index,df[i],s,label=i)
plt.legend(loc=1)
<matplotlib.legend.Legend at 0x13e6e3dd0>
方法二:使用pandas对象调用.plot()函数
# Pandas对象可以直接调用画图方法,如DataFrame.plot(),
# index作为x轴数据,values作为y轴数据,columns作为多个图形的label
df.plot(
style=['','r+','go--'], # style参数:传递列表或字典设置风格,包括颜色color、标记marker和线类型linestyle
alpha=0.9, # alpha参数:设置不透明度
legend=True
)
<Axes: >
# 导入数据,分析每天的销售额
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.groupby('订单日期')['销售额'].sum()
data
订单日期
2015-01-01 11926.250
2015-01-02 32997.160
2015-01-03 17063.172
2015-01-04 7793.184
2015-01-05 9312.184
...
2018-12-25 2454.648
2018-12-26 14559.160
2018-12-28 3936.912
2018-12-29 32578.588
2018-12-30 29277.668
Name: 销售额, Length: 1239, dtype: float64
# 索引和切片
data['2018'] # 2018年的记录
data['2018-02'] # 2018年2月的记录
data['2018-12-01':'2018-12-15'] # 切片
订单日期 2018-12-01 12384.680 2018-12-02 2229.304 2018-12-03 5676.244 2018-12-04 48955.564 2018-12-05 4650.100 2018-12-07 19255.712 2018-12-08 7850.528 2018-12-09 13185.676 2018-12-10 27484.660 2018-12-11 14491.498 2018-12-12 10353.000 2018-12-14 30329.656 2018-12-15 67945.472 Name: 销售额, dtype: float64
重采样 resample
上面的操作中,对日期进行groupby分组聚合后,时间戳是每天(D),如果想要将其转换为每月(M),可以通过重新采样来实现。重新采样是指将时间序列从一个频率转换为另一个频率的过程。将更高频率的数据聚合到低频率被称为向下采样,反之则称为向上采样。
时间序列数据的聚合可以看作是groupby的特殊用例,被称为重采样。
Pandas对象配有resample()方法,与groupby()方法类似,调用时需要对日期时间数据分组,之后再调用聚合函数。
# 重新采样 Resample
data = data['2018'].resample('M',kind='period').sum() # 按月进行重新采样,传递kind参数以时间区间返回
data
订单日期 2018-01 222862.829 2018-02 291814.068 2018-03 399711.781 2018-04 335531.357 2018-05 637673.162 2018-06 565523.427 2018-07 340308.682 2018-08 596338.806 2018-09 503469.855 2018-10 581870.177 2018-11 468822.543 2018-12 544539.100 Freq: M, Name: 销售额, dtype: float64
导入数据文件'超市.xls',数据分析得到2018年12月每天的销售额和利润,再进行绘图。
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.groupby('订单日期')[['销售额','利润']].sum()
data = data.loc['2018-12']
data # 返回DataFrame对象
| 销售额 | 利润 | |
|---|---|---|
| 订单日期 | ||
| 2018-12-01 | 12384.680 | 3026.800 |
| 2018-12-02 | 2229.304 | -149.016 |
| 2018-12-03 | 5676.244 | 713.384 |
| 2018-12-04 | 48955.564 | 15586.984 |
| 2018-12-05 | 4650.100 | 537.460 |
| 2018-12-07 | 19255.712 | 554.652 |
| 2018-12-08 | 7850.528 | 1658.188 |
| 2018-12-09 | 13185.676 | 2294.096 |
| 2018-12-10 | 27484.660 | 6095.880 |
| 2018-12-11 | 14491.498 | 3970.498 |
| 2018-12-12 | 10353.000 | 4011.840 |
| 2018-12-14 | 30329.656 | -392.084 |
| 2018-12-15 | 67945.472 | 17257.772 |
| 2018-12-16 | 30721.810 | -5312.090 |
| 2018-12-17 | 24825.696 | 5394.396 |
| 2018-12-18 | 17503.752 | 1215.032 |
| 2018-12-19 | 3689.952 | 158.032 |
| 2018-12-21 | 29249.052 | 5643.932 |
| 2018-12-22 | 37007.124 | 3732.764 |
| 2018-12-23 | 14877.632 | 2424.632 |
| 2018-12-24 | 39065.012 | -166.208 |
| 2018-12-25 | 2454.648 | -519.512 |
| 2018-12-26 | 14559.160 | -2258.900 |
| 2018-12-28 | 3936.912 | 1185.492 |
| 2018-12-29 | 32578.588 | 7282.128 |
| 2018-12-30 | 29277.668 | 610.288 |
# 绘制时间序列图
import matplotlib.pyplot as plt
%matplotlib notebook
data.plot(
style=['o-','^--'],
alpha=0.8,
title='2018年12月销售额和利润',
ylabel='数值'
)
# plt.title('2018年12月销售额和利润')
# plt.ylabel('数值')
plt.rcParams['font.sans-serif'] = ['SimHei'] # 对中文字体进行指定,可以更改字体'SimHei'
plt.rcParams['axes.unicode_minus'] = False # 指定中文字体后正常显示负号
plt.tight_layout() # 紧密布局,使标题、文本等完整显示
<Axes: title={'center': '2018年12月销售额和利润'}, xlabel='订单日期', ylabel='数值'>
导入数据文件'超市.xls',数据分析得到2018年每个月的销售额,再进行绘图。
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.groupby('订单日期')['销售额'].sum()
data = data['2018']
data = data.resample('M',kind='period').sum() # 按月进行重新采样,传递kind参数以时间区间返回
data # 返回一个Series对象
订单日期 2018-01 222862.829 2018-02 291814.068 2018-03 399711.781 2018-04 335531.357 2018-05 637673.162 2018-06 565523.427 2018-07 340308.682 2018-08 596338.806 2018-09 503469.855 2018-10 581870.177 2018-11 468822.543 2018-12 544539.100 Freq: M, Name: 销售额, dtype: float64
# 绘制时间序列图
data.plot(
style='c^-.',
alpha=0.8,
title='2018年每月销售额',
ylabel='销售额',
)
<Axes: title={'center': '2018年每月销售额'}, xlabel='订单日期', ylabel='销售额'>
导入数据文件'超市.xls',数据分析得到2018年不同产品类别的每个月销售额,再进行绘图。
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
# data = df[df['订单日期'].array.year==2018]
data = df.groupby(['订单日期','类别'])['销售额'].sum() # 返回一个MultiIndex的Series对象
data = data.unstack() # unstack(拆堆):将行中的数据透视到列(index --> columns)
data = data.loc['2018'] # DataFrame对象的行索引
data = data.resample('M',kind='period').sum() # 按月进行重新采样,传递kind参数以时间区间返回
data # 返回一个DataFrame对象
| 类别 | 办公用品 | 家具 | 技术 |
|---|---|---|---|
| 订单日期 | |||
| 2018-01 | 93013.004 | 57794.065 | 72055.760 |
| 2018-02 | 103612.264 | 105159.152 | 83042.652 |
| 2018-03 | 74204.872 | 189553.133 | 135953.776 |
| 2018-04 | 127448.440 | 127647.625 | 80435.292 |
| 2018-05 | 199304.364 | 192803.674 | 245565.124 |
| 2018-06 | 173380.844 | 202455.603 | 189686.980 |
| 2018-07 | 67670.456 | 118898.122 | 153740.104 |
| 2018-08 | 180607.924 | 228681.558 | 187049.324 |
| 2018-09 | 143776.612 | 219038.799 | 140654.444 |
| 2018-10 | 170600.668 | 222196.261 | 189073.248 |
| 2018-11 | 137695.404 | 142531.879 | 188595.260 |
| 2018-12 | 204151.696 | 185287.788 | 155099.616 |
# 另一种数据分析方法
data = df.pivot_table(index=['订单日期'],columns=['类别'],
values=['销售额'], aggfunc='sum')
data = data.loc['2018'] # DataFrame对象的行索引
data = data.resample('M',kind='period').sum() # 重采样resample
data # 返回一个DataFrame对象
| 销售额 | |||
|---|---|---|---|
| 类别 | 办公用品 | 家具 | 技术 |
| 订单日期 | |||
| 2018-01 | 93013.004 | 57794.065 | 72055.760 |
| 2018-02 | 103612.264 | 105159.152 | 83042.652 |
| 2018-03 | 74204.872 | 189553.133 | 135953.776 |
| 2018-04 | 127448.440 | 127647.625 | 80435.292 |
| 2018-05 | 199304.364 | 192803.674 | 245565.124 |
| 2018-06 | 173380.844 | 202455.603 | 189686.980 |
| 2018-07 | 67670.456 | 118898.122 | 153740.104 |
| 2018-08 | 180607.924 | 228681.558 | 187049.324 |
| 2018-09 | 143776.612 | 219038.799 | 140654.444 |
| 2018-10 | 170600.668 | 222196.261 | 189073.248 |
| 2018-11 | 137695.404 | 142531.879 | 188595.260 |
| 2018-12 | 204151.696 | 185287.788 | 155099.616 |
# 绘制时间序列图:风格设置为圆圈实线、方块虚线、三角点线
data.plot(
style=['o-','s--','^-.'],
alpha=0.8,
title='2018年不同产品类别的每月销售额',
ylabel='销售额',
)
plt.legend(loc=4)
<matplotlib.legend.Legend at 0x14f93b5d0>
柱状图是针对离散型数据(比如商品种类、性别等)所作的统计图。每一根柱子代表一个类别,柱子的高度是这个类别的数据统计量(计数、求和、求平均等)。若是竖着的柱子,成为柱状图;若是横着的柱子,成为条形图,本质上并没有什么区别,只是展示方向不同。
堆积柱状图和柱状图的本质是一样的,都是在展现不同类别的数据统计量。只不过简单的柱状图只涉及一个离散型变量(如商品类别),而堆积柱状图涉及两个离散型变量(如商品类别和客户细分)。因为涉及到两个离散型变量,每一个柱子可以代表一个类别,每一个堆叠代表另一个类别(子分类),那么这个子分类就需要通过其他手段(比如颜色最为常见)来进行区分。这样一来就需要一个额外的标签,标注该变量的不同类别所对应的颜色。
plot.bar()函数¶import numpy as np
import pandas as pd
%config InlineBackend.figure_format = 'retina'
s = pd.Series(np.random.randint(20,size=10),index=list('abcdefghij'))
# s.plot(kind='bar')
s.plot.bar()
# index作为X轴的类别,values作为y轴数值
<Axes: >
# 延伸1:设置柱形的颜色和宽度
s.plot.bar(
color='green', # 设置颜色为green
alpha=0.8, # 设置不透明度为0.8
width=0.8 # 设置柱形宽度为0.8
)
<Axes: >
# 延伸2:调用matplotlib中内置的colormaps,使颜色随值的大小而变化
# cmap颜色参考:https://matplotlib.org/tutorials/colors/colormaps.html#sphx-glr-tutorials-colors-colormaps-py
import matplotlib.pyplot as plt
s.plot.bar(
color=plt.get_cmap('Greens')(s/max(s)),
alpha=0.8,
width=0.8
)
<Axes: >
plot.bar()函数¶import numpy as np
import pandas as pd
data=pd.DataFrame(np.random.randint(20,size=(6,4)),
index=pd.date_range('20201001',periods=6).date,
columns=list('ABCD'))
# index作为x轴的类别,values作为y轴的数据,columns作为另一个类别进行分组group或堆叠stack
# data.plot.bar()
data.plot.barh(
stacked=True, # 默认为group分组,设置为堆叠
alpha=0.8, # 设置不透明度为0.8
rot=40 # 设置label旋转角度
)
data
| A | B | C | D | |
|---|---|---|---|---|
| 2020-10-01 | 7 | 3 | 4 | 19 |
| 2020-10-02 | 18 | 18 | 0 | 6 |
| 2020-10-03 | 7 | 5 | 10 | 2 |
| 2020-10-04 | 15 | 8 | 16 | 4 |
| 2020-10-05 | 2 | 13 | 6 | 9 |
| 2020-10-06 | 19 | 2 | 9 | 9 |
导入数据文件'超市.xls',数据分析得到每个商品子类别的销售额,返回的Series对象直接调用plot.barh()函数进行绘制。
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.groupby('子类别')['销售额'].sum()
data # 返回Series对象
子类别 书架 2310821.100 信封 288266.300 器具 2165430.708 复印机 1995047.880 收纳具 1166020.100 标签 97582.100 桌子 862010.429 椅子 2091674.256 用具 482315.820 用品 288818.376 电话 1802056.564 系固件 129241.728 纸张 264361.020 美术 197354.864 装订机 292697.888 设备 879102.448 配件 804746.656 Name: 销售额, dtype: float64
# 绘制条形图:使用colormaps中'Blues',柱形颜色随着销售额大小而变化
data.plot.barh(
color=plt.get_cmap('Blues')(data/(max(data))),
width=0.8,
title='每个商品子类别的销售额',
xlabel='销售额'
)
<Axes: title={'center': '每个商品子类别的销售额'}, xlabel='销售额', ylabel='子类别'>
# 延伸:设置一个阈值(比如10**6),销售额大于阈值的子类别用颜色'#E15759'显示,否则用颜色'#BAB0AC'显示
thred = 10**6
colors=['#BAB0AC']*data.shape[0]
for i in range(data.shape[0]):
if data[i]>thred:
colors[i]='#E15759'
data.plot.barh(
color=colors,
width=0.8,
title='每个商品子类别的销售额',
xlabel='销售额'
)
<Axes: title={'center': '每个商品子类别的销售额'}, xlabel='销售额', ylabel='子类别'>
导入数据文件'超市.xls',数据分析得到每个商品子类别中不同客户细分的销售额,返回的DataFrame对象调用plot.bar()函数进行绘制。
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.groupby(['细分','子类别'])['销售额'].sum() # 返回一个MultiIndex的Series对象
data = data.unstack(level=0) # unstack(拆堆):将行中的数据透视到列,level=0表示将0层index透视到列
data # 返回DataFrame对象
| 细分 | 公司 | 小型企业 | 消费者 |
|---|---|---|---|
| 子类别 | |||
| 书架 | 728370.216 | 458818.948 | 1123631.936 |
| 信封 | 84092.120 | 47966.520 | 156207.660 |
| 器具 | 669985.036 | 349597.836 | 1145847.836 |
| 复印机 | 666012.144 | 343365.736 | 985670.000 |
| 收纳具 | 340742.360 | 223245.400 | 602032.340 |
| 标签 | 28648.760 | 20648.740 | 48284.600 |
| 桌子 | 322034.202 | 118465.725 | 421510.502 |
| 椅子 | 717663.114 | 378771.512 | 995239.630 |
| 用具 | 139173.104 | 90135.444 | 253007.272 |
| 用品 | 93480.660 | 49244.804 | 146092.912 |
| 电话 | 626298.596 | 303042.628 | 872715.340 |
| 系固件 | 38287.452 | 25765.992 | 65188.284 |
| 纸张 | 75666.220 | 43362.060 | 145332.740 |
| 美术 | 70228.172 | 36095.108 | 91031.584 |
| 装订机 | 86339.204 | 42047.628 | 164311.056 |
| 设备 | 223441.652 | 199466.848 | 456193.948 |
| 配件 | 249865.084 | 165745.244 | 389136.328 |
# 另一种数据分析方法
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.pivot_table(index = '子类别', columns = '细分',
values = '销售额', aggfunc = 'sum')
data # 返回DataFrame对象
| 细分 | 公司 | 小型企业 | 消费者 |
|---|---|---|---|
| 子类别 | |||
| 书架 | 728370.216 | 458818.948 | 1123631.936 |
| 信封 | 84092.120 | 47966.520 | 156207.660 |
| 器具 | 669985.036 | 349597.836 | 1145847.836 |
| 复印机 | 666012.144 | 343365.736 | 985670.000 |
| 收纳具 | 340742.360 | 223245.400 | 602032.340 |
| 标签 | 28648.760 | 20648.740 | 48284.600 |
| 桌子 | 322034.202 | 118465.725 | 421510.502 |
| 椅子 | 717663.114 | 378771.512 | 995239.630 |
| 用具 | 139173.104 | 90135.444 | 253007.272 |
| 用品 | 93480.660 | 49244.804 | 146092.912 |
| 电话 | 626298.596 | 303042.628 | 872715.340 |
| 系固件 | 38287.452 | 25765.992 | 65188.284 |
| 纸张 | 75666.220 | 43362.060 | 145332.740 |
| 美术 | 70228.172 | 36095.108 | 91031.584 |
| 装订机 | 86339.204 | 42047.628 | 164311.056 |
| 设备 | 223441.652 | 199466.848 | 456193.948 |
| 配件 | 249865.084 | 165745.244 | 389136.328 |
# 绘制柱形图:不同客户细分类别使用不同颜色,颜色可以参考网站:https://www.materialpalette.com/colors
colors=['#76B7B2','#BAB0AC','#FF9DA7']
data.plot.bar(
stacked=True,
alpha=0.8,
width=0.8,
color=colors,
title='每个商品子类别中不同客户细分的销售额',
ylabel='销售额'
)
<Axes: title={'center': '每个商品子类别中不同客户细分的销售额'}, xlabel='子类别', ylabel='销售额'>
饼图是一种使用非常广泛的统计图,与柱状图一样,都是针对离散型数据的统计图。柱状图多用于展示频数,而饼图多用于展示频率(比例)。一个圆代表整体,然后把它切成楔形,每一个楔形代表某一个类别,所有楔形所占百分比的总和应该等于100%。饼图的块数不宜过多(将比例过少的种类归为一类,称为其它),也不宜过少(离散型变量只有两个取值时不建议画饼图),不多不少刚刚好。
# help(plt.pie)
plot.pie() 函数¶# 生成一个Series对象
import numpy as np
import pandas as pd
s = pd.Series(np.random.randint(20,size=5),index=list('abcde'))
s
a 4 b 9 c 5 d 10 e 6 dtype: int64
# Series对象调用 plot.pie() 函数
s.name='' # 将Series对象的name设置为'',否则会显示None
s.plot.pie(
autopct='%.2f%%', # 设置百分比显示格式(两位浮点数)
# autopct='%i%%' # 设置百分比显示格式(整数)
pctdistance=0.7, # 设置百分比显示的距离,默认0.6
labeldistance=1.1, # 设置标签显示的距离,默认1.1
explode=[0,0.1,0,0,0], # 设置某块楔形突出(传递与data长度相同的序列)
startangle=90, # 设置起始位置的角度
counterclock=True, # 设置饼图的方向,默认逆时针
wedgeprops=dict(width=0.6) # 设置饼图内径宽度,默认1.0,可将其变为环形饼图(Donut Chart)
)
<Axes: >
导入数据文件'超市.xls',数据分析得到每个地区的销售额,返回的Series对象直接调用plot.pie()函数进行绘制,并将占比最小地区突出显示,并在合适的位置注释出销售额总量。
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.groupby('地区')['销售额'].sum()
data
地区 东北 2711223.389 中南 4147884.013 华东 4692464.994 华北 2447301.017 西北 815550.316 西南 1303124.508 Name: 销售额, dtype: float64
# 求销售额总额
# df['销售额'].sum()
data.sum()
16117548.236999998
# 绘制饼图:将销售额最低的扇区突出显示,并在合适位置添加注释文本
ls=[0.] *data.shape[0] # 创建一个值为0,长度与data相等的列表ls
ls[data.argmin()]=0.1 # 获取data中最小值的位置索引,将ls中同等位置的值设置为0.1
data.plot.pie(
autopct='%.2f%%',
explode=ls, # 将ls传递给explode,最小值突出显示
startangle=90 # 将起始位置设置为90度处
)
# 添加注释文本
plt.text(0.6,1.2,s='销售额总量:'+str(np.rint(data.sum())),fontsize=12)
Text(0.6, 1.2, '销售额总量:16117548.0')
导入数据文件'超市.xls',数据分析得到办公用品各子类别的销售额,返回的Series对象直接调用plot.pie()函数进行绘制。由于子类别较多,将销售额最低的4个子类别合并为其它,并突出显示。
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df[df['类别']=='办公用品'] # 筛选办公用品类别
data = data.groupby(['子类别'])['销售额'].sum() # 根据子类别分组,得到每个子类别销售总额
data0 = pd.Series(data.sort_values()[:4].sum(),index=['其它']) # 将销售额最低的四个子类别销量求和,并转化为一个Series对象赋值给data0
data1 = data.sort_values()[4:] # 将其余子类别切片赋值给data1
data = pd.concat([data0,data1]).sort_index() # 使用concat()函数将data0和data1沿0轴堆叠
data.name = '办公用品各子类别销售额' # 设置Series对象的name属性,作为饼图的标题
data
信封 288266.300 其它 688539.712 器具 2165430.708 收纳具 1166020.100 用品 288818.376 装订机 292697.888 Name: 办公用品各子类别销售额, dtype: float64
# 绘制饼图
data.plot.pie(
autopct='%.2f%%', # 百分比显示两位浮点数
pctdistance=0.7, # 设置百分比显示的距离0.7
explode=[0,0.1,0,0,0,0], # 突出显示"其它"
counterclock=False, # 设置饼图的方向为顺时针
)
<Axes: ylabel='办公用品各子类别销售额'>
直方图是针对连续型变量所作的统计图。直方图的横轴是实数轴,可以被分为许多连续的区间。
直方图最大的用处是观察数据分布的形态(如正态分布、右偏、左偏、t分布等),了解数据的取值范围。
一定要区分柱状图和直方图:
plot.hist() 函数¶# 生成一个Series对象
import numpy as np
import pandas as pd
s = pd.Series(np.random.randn(200)) # 生成服从0-1分布的200个随机样本
s
0 0.590812
1 0.852214
2 0.643126
3 -0.175887
4 -1.270769
...
195 0.549132
196 -0.382942
197 0.190968
198 -0.676767
199 -1.080572
Length: 200, dtype: float64
# help(plt.hist)
# Series对象调用 plot.hist() 函数
s.plot.hist(
bins=10, # 设置数据桶的个数
density=False, # 默认为False显示频数,如果设置为Ture则显示密度,所有数值之和作归一化
range=[-2,2], # 设置数据显示的区间(起始位置和终止位置)
orientation='vertical', # 设置直方图的方向
color='#0097a7', # 设置颜色
alpha=0.8 # 设置不透明度
)
<Axes: ylabel='Frequency'>
导入数据文件'超市.xls',数据分析得到每笔订单的利润,绘制体现利润分布情况的直方图,并在合适位置注释出利润平均值。
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data = df.groupby('订单 ID')['利润'].sum()
data
订单 ID
CN-2015-1017090 112.280
CN-2015-1031662 399.420
CN-2015-1047687 23.520
CN-2015-1070056 2866.080
CN-2015-1086295 2846.620
...
US-2018-5822232 -1664.936
US-2018-5835982 -907.732
US-2018-5840887 2758.854
US-2018-5895003 -654.416
US-2018-5897280 -50.652
Name: 利润, Length: 2773, dtype: float64
# 利润平均值
data.mean()
777.6990685178507
# 绘制直方图:X轴数据显示区间设置为[-1000,2000],颜色设置为'#0097a7',并在合适位置添加注释文本
data.plot.hist(
range=[-1000,2000],
color='#0097a7',
alpha=0.8,
title='利润分布情况',
xlabel='利润'
)
# 添加注释文本
plt.text(1000,600,s='平均利润: '+str(np.round(data.mean(),3)),fontsize=12)
Text(1000, 600, '平均利润: 777.699')
散点图是用于展示两个连续型变量的一种常用统计图。散点图中的每一个点由横纵两个坐标值组成,可以用来解读两个变量的相关关系,比如正线性相关、负线性相关、非线性相关和不相关等。散点图的基本框架(用于比较两个变量):
气泡图可以看作是带有“气泡”维度的散点图。这种图表类型的优势在于它便于我们一次比较三个连续型变量。一个变量是x轴,一个变量是y轴,而第三个则通过气泡的面积大小(还可以通过颜色)来体现。气泡图的基本框架(用于比较三个变量):
plt.scatter()函数绘制散点图和气泡图¶# 使用基本流程绘制散点图
N = 100
x = np.linspace(0,1,N) # 返回 N个 0-1 之间均匀分布的样本
y = np.random.randn(N) # 返回 N个服从标准正态分布的随机样本
plt.scatter(
x,y,
c='#E15759', # 设置样本点的颜色
s=80, # 设置样本点的大小
alpha=0.5, # 设置不透明度
)
plt.show()
# 使用基本流程绘制气泡
N = 100
x = np.linspace(0,1,N) # 返回 N个 0-1 之间均匀分布的样本
y = np.random.randn(N) # 返回 N个服从标准正态分布的随机样本
z = np.random.randint(50,size=N) # 返回 N个50以内的随机整数
plt.scatter(
x,y,
c=z, # 将数值z传递给颜色参数c
cmap='cividis', # 设置颜色范围,可参考colormap
s=z*10, # 将数值z传递给大小参数s
alpha=0.5, # 设置不透明度
)
plt.show()
导入数据文件'超市.xls',数据分析得到“设备”这一类别商品的销售额和利润,使用plt.scatter()函数进行绘制。
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data=df[df['子类别']=='设备'] # 筛选子类别为设备的记录
data.head()
| 订单 ID | 订单日期 | 发货日期 | 邮寄方式 | 客户 ID | 客户名称 | 细分 | 城市 | 省/自治区 | 国家 | 地区 | 产品 ID | 类别 | 子类别 | 产品名称 | 销售额 | 数量 | 折扣 | 利润 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 行 ID | |||||||||||||||||||
| 6 | CN-2016-4497736 | 2016-10-27 | 2016-10-31 | 标准级 | 俞明-18325 | 俞明 | 消费者 | 景德镇 | 江西 | 中国 | 华东 | 技术-设备-10001640 | 技术 | 设备 | 柯尼卡 打印机, 红色 | 11129.58 | 9 | 0.0 | 3783.78 |
| 11 | CN-2015-4195213 | 2015-12-22 | 2015-12-24 | 二级 | 谢雯-21700 | 谢雯 | 小型企业 | 榆林 | 陕西 | 中国 | 西北 | 技术-设备-10000001 | 技术 | 设备 | 爱普生 计算器, 耐用 | 434.28 | 2 | 0.0 | 4.20 |
| 62 | CN-2017-4162714 | 2017-06-07 | 2017-06-10 | 一级 | 马丽-16480 | 马丽娜 | 消费者 | 邓州 | 河南 | 中国 | 中南 | 技术-设备-10004500 | 技术 | 设备 | 松下 电话, 耐用 | 2775.36 | 7 | 0.0 | 332.22 |
| 65 | CN-2018-5922906 | 2018-09-15 | 2018-09-17 | 二级 | 钱伟-21430 | 钱伟 | 公司 | 湛江 | 广东 | 中国 | 中南 | 技术-设备-10003635 | 技术 | 设备 | StarTech 打印机, 耐用 | 4784.08 | 4 | 0.0 | 382.48 |
| 99 | CN-2018-5834341 | 2018-11-10 | 2018-11-14 | 标准级 | 马惠-14335 | 马惠英 | 公司 | 南宁 | 广西 | 中国 | 中南 | 技术-设备-10003039 | 技术 | 设备 | 爱普生 收据打印机, 耐用 | 2186.80 | 4 | 0.0 | 612.08 |
# 绘制散点图,点的颜色和大小固定
plt.scatter(
x=data['销售额'],
y=data['利润'],
s=50,
c='c',
alpha=0.8
)
plt.title('设备类商品销售额和利润分布情况')
plt.xlabel('销售额')
plt.ylabel('利润')
plt.tight_layout()
plt.show()
# 还可进一步分析相关关系的原因,将'折扣'作为颜色传递参数
plt.scatter(
x=data['销售额'],
y=data['利润'],
c=data['折扣'],
alpha=0.8
)
plt.title('设备类商品销售额和利润分布情况')
plt.xlabel('销售额')
plt.ylabel('利润')
plt.tight_layout()
plt.show()
导入数据文件'超市.xls',数据分析得到2018年浙江省每一笔订单的销售额、利润和购买商品数量,使用plt.scatter()函数绘制气泡图。
# 数据分析
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
df = df[(df['订单日期'].array.year==2018)&(df['省/自治区']=='浙江')]
data = df.groupby('订单 ID')[['销售额','利润','数量']].sum()
data
| 销售额 | 利润 | 数量 | |
|---|---|---|---|
| 订单 ID | |||
| CN-2018-1182921 | 7114.968 | -1460.732 | 16 |
| CN-2018-1246652 | 10696.588 | -4138.232 | 13 |
| CN-2018-1510100 | 14931.084 | -7248.136 | 30 |
| CN-2018-1516102 | 1130.388 | 396.228 | 17 |
| CN-2018-1574908 | 6917.400 | -471.240 | 12 |
| ... | ... | ... | ... |
| US-2018-5180154 | 3545.696 | -439.264 | 24 |
| US-2018-5235171 | 1621.648 | -795.592 | 5 |
| US-2018-5315398 | 189.000 | -37.800 | 5 |
| US-2018-5444149 | 165.480 | -24.920 | 5 |
| US-2018-5726249 | 14059.892 | -6876.828 | 20 |
65 rows × 3 columns
# 绘制气泡图:点的颜色大小都随数量的大小而变化
plt.scatter(
x=data['销售额'],
y=data['利润'],
s=data['数量']*15,
c=data['数量'],
alpha=0.6
)
plt.title('2018年浙江省订单销售额和利润分布情况')
plt.xlabel('销售额')
plt.ylabel('利润')
plt.tight_layout()
plt.show()
# 延伸:不同类别的订单使用不同颜色进行标记
# 数据分析:2018年浙江省不同类别订单的销售额和利润
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
df = df[(df['订单日期'].array.year==2018)&(df['省/自治区']=='浙江')]
data = df.groupby(['类别','订单 ID'])[['销售额','利润']].sum()
data
| 销售额 | 利润 | ||
|---|---|---|---|
| 类别 | 订单 ID | ||
| 办公用品 | CN-2018-1182921 | 47.040 | -18.060 |
| CN-2018-1246652 | 421.120 | 37.800 | |
| CN-2018-1510100 | 5397.924 | -2004.016 | |
| CN-2018-1516102 | 1130.388 | 396.228 | |
| CN-2018-1574908 | 2153.256 | 349.216 | |
| ... | ... | ... | ... |
| 技术 | US-2018-4479623 | 3186.792 | -425.208 |
| US-2018-4774798 | 245.952 | -45.108 | |
| US-2018-4988987 | 2110.416 | -70.504 | |
| US-2018-5180154 | 601.020 | -95.340 | |
| US-2018-5235171 | 1553.328 | -802.872 |
101 rows × 2 columns
# 绘制气泡图,将plt.scatter()函数写入for循环中
# 三个类别的颜色设置为['#4db6ac','#ff8a65','#78909c'],建议写入for循环中
colors = ['#4db6ac','#ff8a65','#78909c']
for i,c in zip(data.index.levels[0],colors):
plt.scatter(
x=data.loc[i,['销售额']],
y=data.loc[i,['利润']],
label=i,
alpha=0.6,
s=120,
c=c
)
plt.title('2018年浙江省不同类别订单销售额和利润分布情况')
plt.xlabel('销售额')
plt.ylabel('利润')
plt.legend()
plt.show()
对于pandas对象(Series和DataFrame),我们学习了直接使用pandas对象调用绘图方法,比如.plot()绘制线形图、.plot.bar()绘制柱状图、.plot.pie()绘制饼图、.plot.hist()绘制直方图。因此在绘制多子图时,pandas对象可以直接调用绘图方法,然后再将子图的轴axs传递给参数ax即可。
而对于散点图/气泡图Scatter,需要使用轴axs调用.scatter()函数进行绘制。
# 创建一个Series对象
import pandas as pd
N = 100
s = pd.Series(np.random.randn(N),index=np.linspace(0,1,N))
s
0.000000 -2.122179
0.010101 0.473741
0.020202 0.640655
0.030303 0.051061
0.040404 -1.177804
...
0.959596 0.304418
0.969697 0.253425
0.979798 0.767768
0.989899 0.424501
1.000000 0.033509
Length: 100, dtype: float64
# Series对象绘制多子图
# import matplotlib.pyplot as plt
# %matplotlib notebook
# 使用plt.subplots()创建图片和多个轴
fig, axs = plt.subplots(2, 2) # 创建一张2*2个轴的图片,返回以生成子图对象的NumPy数组
# 使用pandas对象直接调用绘图方法,将轴传递给ax参数即可
s.plot(ax=axs[0,0], style='c')
s.plot.hist(ax=axs[0,1], grid=False)
# 对于scatter函数,使用基本流程绘制
axs[1,0].scatter(s.index,s)
axs[1,1].scatter(s.index,s,c=s,s=(s+5)*5)
# axs[1,0].plot(s.index,s,color='c')
# axs[1,1].hist(s)
# 进一步设置标题和X\Y轴标题
axs[0,0].set_title('Line Plot')
axs[0,1].set_title('Hist Plot')
axs[1,0].set_ylabel('Y-Axis')
axs[1,1].set_xlabel('X-Axis')
Text(0.5, 0, 'X-Axis')
# 数据分析:得到2018年每个月的销售额和利润
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
data1 = df.groupby(['订单日期'])[['销售额','利润']].sum()
data1 = data1.loc['2018'].resample('M',kind='period').sum()
data1
| 销售额 | 利润 | |
|---|---|---|
| 订单日期 | ||
| 2018-01 | 222862.829 | 33526.269 |
| 2018-02 | 291814.068 | 35249.508 |
| 2018-03 | 399711.781 | 56542.521 |
| 2018-04 | 335531.357 | 32848.277 |
| 2018-05 | 637673.162 | 67969.062 |
| 2018-06 | 565523.427 | 81670.267 |
| 2018-07 | 340308.682 | 58861.922 |
| 2018-08 | 596338.806 | 58370.326 |
| 2018-09 | 503469.855 | 80374.595 |
| 2018-10 | 581870.177 | 73466.617 |
| 2018-11 | 468822.543 | 31554.383 |
| 2018-12 | 544539.100 | 74556.440 |
# 数据分析:得到2018年每个月的订单数量
data2 = df.drop_duplicates('订单 ID') # 删除重复的订单ID
data2 = data2.groupby('订单日期')['订单 ID'].count()
data2 = data2.loc['2018'].resample('M',kind='period').sum()
data2
订单日期 2018-01 37 2018-02 47 2018-03 42 2018-04 48 2018-05 93 2018-06 81 2018-07 61 2018-08 83 2018-09 72 2018-10 85 2018-11 81 2018-12 87 Freq: M, Name: 订单 ID, dtype: int64
# 绘制多子图
fig, axs = plt.subplots(nrows=2, ncols=1)
data1.plot(ax=axs[0],style=['o-','s--'], sharex=True)
data2.plot.bar(ax=axs[1],rot=40,color=plt.get_cmap('Blues')(data2/(max(data2))))
axs[0].set_title('2018年销售情况分析')
axs[0].set_ylabel('值')
axs[1].set_ylabel('订单数量')
Text(0, 0.5, '订单数量')
# 数据分析:2018年某商品(纸张)在不同地区销售总额
import pandas as pd
df = pd.read_excel('超市.xls', sheet_name='订单', index_col='行 ID')
df = df[(df['订单日期'].array.year==2018)&(df['子类别']=='纸张')]
data1 = df.groupby('地区')['销售额'].sum()
data1
地区 东北 12644.10 中南 21396.20 华东 26290.46 华北 16109.10 西北 2186.10 西南 5861.94 Name: 销售额, dtype: float64
# 数据分析:2018年某商品(纸张)每笔订单的销售额和利润
data2 = df.groupby(['地区','订单 ID'])[['销售额','利润']].sum()
data2
| 销售额 | 利润 | ||
|---|---|---|---|
| 地区 | 订单 ID | ||
| 东北 | CN-2018-1312334 | 450.80 | 144.20 |
| CN-2018-2102819 | 568.40 | 136.22 | |
| CN-2018-2292031 | 1526.56 | 526.96 | |
| CN-2018-2306215 | 237.30 | 101.64 | |
| CN-2018-2560726 | 426.72 | 119.28 | |
| ... | ... | ... | ... |
| 西南 | US-2018-1069751 | 248.08 | 76.72 |
| US-2018-2162302 | 422.80 | 46.20 | |
| US-2018-4339927 | 124.32 | 33.46 | |
| US-2018-4764837 | 451.64 | 157.92 | |
| US-2018-5315398 | 435.96 | 121.80 |
165 rows × 2 columns
# 绘制多子图
fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(8,4))
# 绘制饼图
data1.plot.pie(ax=axs[0],autopct='%i%%',pctdistance=0.7)
# 绘制散点图,使用for循环遍历不同地区
for i in data2.index.levels[0]:
axs[1].scatter(
x = data2.loc[i,'销售额'],
y = data2.loc[i,'利润'],
label = i,
alpha = 0.7
)
axs[0].set_title('纸张在不同地区销售额占比情况')
axs[1].set_title('纸张的销售额和利润分布情况')
axs[1].set_xlabel('Salse')
axs[1].set_ylabel('Profit')
axs[1].legend(loc=4)
<matplotlib.legend.Legend at 0x16d7d1e10>